//
// --------------------------------------------------------------------------
//  Gurux Ltd
// 
//
//
//
// Version:         $Revision: 10624 $,
//                  $Date: 2019-04-24 13:56:09 +0300 (ke, 24 huhti 2019) $
//                  $Author: gurux01 $
//
// Copyright (c) Gurux Ltd
//
//---------------------------------------------------------------------------
//
//  DESCRIPTION
//
// This file is a part of Gurux Device Framework.
//
// Gurux Device Framework is Open Source software; you can redistribute it
// and/or modify it under the terms of the GNU General Public License 
// as published by the Free Software Foundation; version 2 of the License.
// Gurux Device Framework is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
// See the GNU General Public License for more details.
//
// More information of Gurux DLMS/COSEM Director: https://www.gurux.org/GXDLMSDirector
//
// This code is licensed under the GNU General Public License v2. 
// Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt
//---------------------------------------------------------------------------


using System;
using System.IO;
using System.Security.Cryptography;

// http://www.codeproject.com/KB/security/DotNetCrypto.aspx?msg=2716557
// Sample encrypt/decrypt functions
//    Parameter checks and error handling are ommited for better readability
//
/// <summary>
/// A helper class for data cryptation/encryptation.
/// </summary>
internal class CryptHelper
{

    /// <summary>
    /// Encrypts a byte array into a byte array using a key and an IV.
    /// </summary>
    /// <param name="clearData">The input on which to perform the operation on.</param>
    /// <param name="key">The key used to encrypt the clearData.</param>
    /// <param name="iv">An Initialization Vector used in encryptation.</param>
    /// <returns>Encrypted data.</returns>
    public static byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)
    {
        // Create a MemoryStream that is going to accept the encrypted bytes
        MemoryStream ms = new MemoryStream();

        // Create a symmetric algorithm.
        // We are going to use Rijndael because it is strong and available on all platforms.
        // You can use other algorithms, to do so substitute the next line with something like
        //                      TripleDES alg = TripleDES.Create();
        Rijndael alg = Rijndael.Create();

        // Now set the key and the IV.
        // We need the IV (Initialization Vector) because the algorithm is operating in its default
        // mode called CBC (Cipher Block Chaining). The IV is XORed with the first block (8 byte)
        // of the data before it is encrypted, and then each encrypted block is XORed with the
        // following block of plaintext. This is done to make encryption more secure.
        // There is also a mode called ECB which does not need an IV, but it is much less secure.
        alg.Key = key;
        alg.IV = iv;

        // Create a CryptoStream through which we are going to be pumping our data.
        // CryptoStreamMode.Write means that we are going to be writing data to the stream
        // and the output will be written in the MemoryStream we have provided.
        CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);

        // Write the data and make it do the encryption
        cs.Write(clearData, 0, clearData.Length);

        // Close the crypto stream (or do FlushFinalBlock).
        // This will tell it that we have done our encryption and there is no more data coming in,
        // and it is now a good time to apply the padding and finalize the encryption process.
        cs.Close();

        // Now get the encrypted data from the MemoryStream.
        // Some people make a mistake of using GetBuffer() here, which is not the right way.
        byte[] encryptedData = ms.ToArray();

        return encryptedData;
    }

    /// <summary>
    /// Encrypts a string into a string using a password. Uses Encrypt(byte[], byte[], byte[]).
    /// </summary>
    /// <param name="clearText">The input on which to perform the operation on.</param>
    /// <param name="password">The password used in the encryptation.</param>
    /// <returns>Encrypted text.</returns>
    public static string Encrypt(string clearText, string password)
    {
        // First we need to turn the input string into a byte array.
        byte[] clearBytes = System.Text.Encoding.ASCII.GetBytes(clearText);

        // Then, we need to turn the password into Key and IV
        // We are using salt to make it harder to guess our key using a dictionary attack -
        // trying to guess a password by enumerating all possible words.

        // This salt should be changed to random, instead of harcoding it.
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        // Now get the key/IV and do the encryption using the function that accepts byte arrays.
        // Using PasswordDeriveBytes object we are first getting 32 bytes for the Key
        // (the default Rijndael key length is 256bit = 32bytes) and then 16 bytes for the IV.
        // IV should always be the block size, which is by default 16 bytes (128 bit) for Rijndael.
        // If you are using DES/TripleDES/RC2 the block size is 8 bytes and so should be the IV size.
        // You can also read KeySize/BlockSize properties off the algorithm to find out the sizes.
        byte[] encryptedData = Encrypt(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));

        // Now we need to turn the resulting byte array into a string.
        // A common mistake would be to use an Encoding class for that. It does not work
        // because not all byte values can be represented by characters.
        // We are going to be using Base64 encoding that is designed exactly for what we are
        // trying to do.
        return Convert.ToBase64String(encryptedData);

    }

    /// <summary>
    /// Encrypt bytes into bytes using a password. Uses Encrypt(byte[], byte[], byte[]).
    /// </summary>
    /// <param name="clearData">The input on which to perform the operation on.</param>
    /// <param name="password">The password used in the encryptation.</param>
    /// <returns>Encrypted data.</returns>
    public static byte[] Encrypt(byte[] clearData, string password)
    {
        // We need to turn the password into Key and IV.
        // We are using salt to make it harder to guess our key using a dictionary attack -
        // trying to guess a password by enumerating all possible words.
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        // Now get the key/IV and do the encryption using the function that accepts byte arrays.
        // Using PasswordDeriveBytes object we are first getting 32 bytes for the Key
        // (the default Rijndael key length is 256bit = 32bytes) and then 16 bytes for the IV.
        // IV should always be the block size, which is by default 16 bytes (128 bit) for Rijndael.
        // If you are using DES/TripleDES/RC2 the block size is 8 bytes and so should be the IV size.
        // You can also read KeySize/BlockSize properties off the algorithm to find out the sizes.
        return Encrypt(clearData, pdb.GetBytes(32), pdb.GetBytes(16));

    }


    /// <summary>
    /// Encrypts a file into another file using a password
    /// </summary>
    /// <param name="fileIn">Input file.</param>
    /// <param name="fileOut">Output file.</param>
    /// <param name="password">The password used in the encryptation.</param>
    public static void Encrypt(string fileIn, string fileOut, string password)
    {
        // First we are going to open the file streams
        FileStream fsIn = new FileStream(fileIn, FileMode.Open, FileAccess.Read);
        FileStream fsOut = new FileStream(fileOut, FileMode.OpenOrCreate, FileAccess.Write);

        // Then we are going to derive a Key and an IV from the Password and create an algorithm
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        Rijndael alg = Rijndael.Create();

        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);

        // Now create a crypto stream through which we are going to be pumping data.
        // Our fileOut is going to be receiving the encrypted bytes.
        CryptoStream cs = new CryptoStream(fsOut, alg.CreateEncryptor(), CryptoStreamMode.Write);

        // Now will will initialize a buffer and will be processing the input file in chunks.
        // This is done to avoid reading the whole file (which can be huge) into memory.
        int bufferLen = 4096;
        byte[] buffer = new byte[bufferLen];
        int bytesRead;

        do
        {
            // read a chunk of data from the input file
            bytesRead = fsIn.Read(buffer, 0, bufferLen);

            // encrypt it
            cs.Write(buffer, 0, bytesRead);

        } while (bytesRead != 0);

        // close everything
        cs.Close(); // this will also close the unrelying fsOut stream
        fsIn.Close();
    }

    /// <summary>
    /// Decrypt a byte array into a byte array using a key and an IV.
    /// </summary>
    /// <param name="cipherData">Input data.</param>
    /// <param name="key">The key used to decrypt.</param>
    /// <param name="iv">An Initialization Vector used in decryptation.</param>
    /// <returns>Clear data.</returns>
    public static byte[] Decrypt(byte[] cipherData, byte[] key, byte[] iv)
    {
        // Create a MemoryStream that is going to accept the decrypted bytes
        MemoryStream ms = new MemoryStream();

        // Create a symmetric algorithm.
        // We are going to use Rijndael because it is strong and available on all platforms.
        // You can use other algorithms, to do so substitute the next line with something like
        //                      TripleDES alg = TripleDES.Create();
        Rijndael alg = Rijndael.Create();

        // Now set the key and the IV.
        // We need the IV (Initialization Vector) because the algorithm is operating in its default
        // mode called CBC (Cipher Block Chaining). The IV is XORed with the first block (8 byte)
        // of the data after it is decrypted, and then each decrypted block is XORed with the previous
        // cipher block. This is done to make encryption more secure.
        // There is also a mode called ECB which does not need an IV, but it is much less secure.
        alg.Key = key;
        alg.IV = iv;

        // Create a CryptoStream through which we are going to be pumping our data.
        // CryptoStreamMode.Write means that we are going to be writing data to the stream
        // and the output will be written in the MemoryStream we have provided.
        CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);

        // Write the data and make it do the decryption
        cs.Write(cipherData, 0, cipherData.Length);

        // Close the crypto stream (or do FlushFinalBlock).
        // This will tell it that we have done our decryption and there is no more data coming in,
        // and it is now a good time to remove the padding and finalize the decryption process.
        cs.Close();

        // Now get the decrypted data from the MemoryStream.
        // Some people make a mistake of using GetBuffer() here, which is not the right way.
        byte[] decryptedData = ms.ToArray();

        return decryptedData;
    }

    /// <summary>
    /// Decrypt a string into a string using a password. Uses Decrypt(byte[], byte[], byte[]).
    /// </summary>
    /// <param name="cipherText">Input text.</param>
    /// <param name="password">The password used in the decryptation.</param>
    /// <returns>Clear text.</returns>
    public static byte[] Decrypt(string cipherText, string password)
    {
        // First we need to turn the input string into a byte array.
        // We presume that Base64 encoding was used
        byte[] cipherBytes = Convert.FromBase64String(cipherText);

        // Then, we need to turn the password into Key and IV
        // We are using salt to make it harder to guess our key using a dictionary attack -
        // trying to guess a password by enumerating all possible words.
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        // Now get the key/IV and do the decryption using the function that accepts byte arrays.
        // Using PasswordDeriveBytes object we are first getting 32 bytes for the Key
        // (the default Rijndael key length is 256bit = 32bytes) and then 16 bytes for the IV.
        // IV should always be the block size, which is by default 16 bytes (128 bit) for Rijndael.
        // If you are using DES/TripleDES/RC2 the block size is 8 bytes and so should be the IV size.
        // You can also read KeySize/BlockSize properties off the algorithm to find out the sizes.
        byte[] decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));

        // Now we need to turn the resulting byte array into a string.
        // A common mistake would be to use an Encoding class for that. It does not work
        // because not all byte values can be represented by characters.
        // We are going to be using Base64 encoding that is designed exactly for what we are
        // trying to do.
        return decryptedData;

    }

    /// <summary>
    /// Decrypt bytes into bytes using a password. Uses Decrypt(byte[], byte[], byte[]).
    /// </summary>
    /// <param name="cipherData">Input data.</param>
    /// <param name="password">The password used in the decryptation.</param>
    /// <returns>Clear data.</returns>
    public static byte[] Decrypt(byte[] cipherData, string password)
    {
        // We need to turn the password into Key and IV.
        // We are using salt to make it harder to guess our key using a dictionary attack -
        // trying to guess a password by enumerating all possible words.
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        // Now get the key/IV and do the Decryption using the function that accepts byte arrays.
        // Using PasswordDeriveBytes object we are first getting 32 bytes for the Key
        // (the default Rijndael key length is 256bit = 32bytes) and then 16 bytes for the IV.
        // IV should always be the block size, which is by default 16 bytes (128 bit) for Rijndael.
        // If you are using DES/TripleDES/RC2 the block size is 8 bytes and so should be the IV size.
        // You can also read KeySize/BlockSize properties off the algorithm to find out the sizes.
        return Decrypt(cipherData, pdb.GetBytes(32), pdb.GetBytes(16));

    }

    /// <summary>
    /// Decrypt a file into another file using a password.
    /// </summary>
    /// <param name="fileIn">Input file.</param>
    /// <param name="fileOut">Output file.</param>
    /// <param name="password">The password used in the decryptation.</param>
    public static void Decrypt(string fileIn, string fileOut, string password)
    {
        // First we are going to open the file streams
        FileStream fsIn = new FileStream(fileIn, FileMode.Open, FileAccess.Read);
        FileStream fsOut = new FileStream(fileOut, FileMode.OpenOrCreate, FileAccess.Write);

        // Then we are going to derive a Key and an IV from the Password and create an algorithm
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        Rijndael alg = Rijndael.Create();

        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);

        // Now create a crypto stream through which we are going to be pumping data.
        // Our fileOut is going to be receiving the Decrypted bytes.
        CryptoStream cs = new CryptoStream(fsOut, alg.CreateDecryptor(), CryptoStreamMode.Write);

        // Now will will initialize a buffer and will be processing the input file in chunks.
        // This is done to avoid reading the whole file (which can be huge) into memory.
        int bufferLen = 4096;
        byte[] buffer = new byte[bufferLen];
        int bytesRead;

        do
        {
            // read a chunk of data from the input file
            bytesRead = fsIn.Read(buffer, 0, bufferLen);

            // Decrypt it
            cs.Write(buffer, 0, bytesRead);

        } while (bytesRead != 0);

        // close everything
        cs.Close(); // this will also close the unrelying fsOut stream
        fsIn.Close();
    }
}